// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//


#pragma once

#include <type_traits>
#include "melon/optional.h"
#include "melon/json.h"
#include <pollux/common/base/exceptions.h>
#include <pollux/common/serialization/deserialization_registry.h>
#include <pollux/common/meta_programming.h>

namespace kumo {
    namespace pollux {
        inline const melon::json::serialization_opts getSerializationOptions() {
            melon::json::serialization_opts opts;
            opts.allow_nan_inf = true;
            opts.allow_trailing_comma = true;
            opts.recursion_limit = UINT_MAX;
            opts.sort_keys = true;
            opts.double_fallback = true;
            return opts;
        }

        template<class T, class... args>
        struct is_any_of : std::false_type {
        };

        template<class T, class arg1, class... args>
        struct is_any_of<T, arg1, args...> : is_any_of<T, args...> {
        };

        template<class T, class... args>
        struct is_any_of<T, T, args...> : std::true_type {
        };

        template<class, class = void>
        struct has_static_obj_create_type : std::false_type {
        };

        template<class T>
        struct has_static_obj_create_type<
                    T,
                    std::enable_if_t<
                        std::is_same_v<decltype(T::create(std::declval<melon::Dynamic>())), T> > >
                : std::true_type {
        };

        template<class>
        struct vector_type {
            using type = void;
        };

        template<class T>
        struct vector_type<std::vector<T> > {
            using type = T;
        };

        template<class T>
        struct is_vector_type
                : std::is_same<T, std::vector<typename vector_type<T>::type> > {
        };

        using serilizationformat = melon::Dynamic;

        template<class, class = void>
        struct has_serialize_type : std::false_type {
        };

        template<class T>
        struct has_serialize_type<
                    T,
                    std::enable_if_t<std::is_same_v<
                        decltype(std::declval<T>().serialize()),
                        serilizationformat> > > : std::true_type {
        };

        class ISerializable {
        public:
            virtual melon::Dynamic serialize() const = 0;

            // Serialization for clases derived from ISerializable.
            template<
                typename T,
                typename = std::enable_if_t<std::is_base_of_v<ISerializable, T> > >
            static melon::Dynamic serialize(
                const std::shared_ptr<const T> &serializable) {
                return serializable->serialize();
            }

            template<
                typename T,
                std::enable_if_t<
                    has_serialize_type<T>::value ||
                    std::is_base_of_v<ISerializable, T>>* = nullptr>
            static melon::Dynamic serialize(const T &obj) {
                return obj.serialize();
            }

            template<
                typename T,
                std::enable_if_t<std::is_same_v<T, std::string> >* = nullptr>
            static melon::Dynamic serialize(const T &val) {
                return val;
            }

            template<
                typename T,
                typename = std::enable_if_t<is_any_of<T, int64_t, double, bool>::value> >
            static melon::Dynamic serialize(T val) {
                return val;
            }

            static melon::Dynamic serialize(int32_t val) {
                return melon::Dynamic{(int64_t) val};
            }

            static melon::Dynamic serialize(uint32_t val) {
                return melon::Dynamic{(int64_t) val};
            }

            static melon::Dynamic serialize(uint64_t val) {
                return melon::Dynamic{(int64_t) val};
            }

            // Serialization for standard containers.
            // TODO separate defintions of composite types from declarations.
            template<typename T>
            static melon::Dynamic serialize(const std::vector<T> &vec) {
                melon::Dynamic arr = melon::Dynamic::array;
                for (const auto &member: vec) {
                    arr.push_back(ISerializable::serialize(member));
                }
                return arr;
            }

            template<
                typename T,
                std::enable_if_t<
                    std::is_same_v<T, melon::Optional<typename T::value_type> > > >
            static melon::Dynamic serialize(const melon::Optional<T> &val) {
                if (!val.hasValue()) {
                    return nullptr;
                }

                return serialize(val.value());
            }

            template<typename T, std::enable_if_t<util::is_mappish<T>::value>* = nullptr>
            static melon::Dynamic serialize(const T &map) {
                melon::Dynamic keys = melon::Dynamic::array;
                melon::Dynamic values = melon::Dynamic::array;
                for (auto &pair: map) {
                    keys.push_back(ISerializable::serialize(pair.first));
                    values.push_back(ISerializable::serialize(pair.second));
                }

                melon::Dynamic obj = melon::Dynamic::object;
                obj["keys"] = std::move(keys);
                obj["values"] = std::move(values);

                return obj;
            }

            template<
                typename T,
                typename = std::enable_if_t<std::is_base_of_v<ISerializable, T> > >
            static std::shared_ptr<const T> deserialize(
                const melon::Dynamic &obj,
                void *context = nullptr) {
                POLLUX_USER_CHECK(obj.isObject());
                // use the key to lookup creator and call it.
                // creator generally be a static method in the class.
                auto name = obj["name"].asString();

                auto res = deserializeAsUniquePtr<T>(obj);
                if (res != nullptr) {
                    return std::dynamic_pointer_cast<const T>(
                        std::shared_ptr(std::move(res)));
                }

                const auto &registryWithContext =
                        pollux::DeserializationWithContextRegistryForSharedPtr();
                if (registryWithContext.Has(name)) {
                    return std::dynamic_pointer_cast<const T>(
                        registryWithContext.Create(name, obj, context));
                }

                const auto &registry = pollux::DeserializationRegistryForSharedPtr();
                POLLUX_USER_CHECK(
                    registry.Has(name),
                    "Deserialization function for class: {} is not registered",
                    name);

                return std::dynamic_pointer_cast<const T>(registry.Create(name, obj));
            }

            template<
                typename T,
                typename = std::enable_if_t<has_static_obj_create_type<T>::value> >
            using createReturnType = decltype(T::create(std::declval<melon::Dynamic>()));

            template<
                typename T,
                typename = std::enable_if_t<has_static_obj_create_type<T>::value> >
            static createReturnType<T> deserialize(
                const melon::Dynamic &obj,
                void *context = nullptr) {
                return T::create(obj);
            }

            template<
                typename T,
                typename =
                std::enable_if_t<std::is_integral_v<T> && !std::is_same_v<T, bool>> >
            static T deserialize(const melon::Dynamic &obj, void *context = nullptr) {
                auto raw = obj.asInt();
                POLLUX_USER_CHECK_GE(raw, std::numeric_limits<T>::min());
                POLLUX_USER_CHECK_LE(raw, std::numeric_limits<T>::max());
                return (T) raw;
            }

            template<typename T, typename = std::enable_if_t<std::is_same_v<T, bool> > >
            static bool deserialize(const melon::Dynamic &obj, void *context = nullptr) {
                return obj.asBool();
            }

            template<typename T, typename = std::enable_if_t<std::is_same_v<T, double> > >
            static double deserialize(
                const melon::Dynamic &obj,
                void *context = nullptr) {
                return obj.asDouble();
            }

            template<
                typename T,
                typename = std::enable_if_t<std::is_same_v<T, std::string> > >
            static std::string deserialize(
                const melon::Dynamic &obj,
                void *context = nullptr) {
                return obj.asString();
            }

            template<
                typename T,
                typename = std::enable_if_t<
                    std::is_same_v<T, melon::Optional<typename T::value_type> > > >
            static melon::Optional<
                decltype(ISerializable::deserialize<typename T::value_type>(
                    std::declval<melon::Dynamic>()))>
            deserialize(const melon::Dynamic &obj, void *context = nullptr) {
                if (obj.isNull()) {
                    return melon::none;
                }
                auto val = deserialize<typename T::value_type>(obj);
                return melon::Optional<
                    decltype(ISerializable::deserialize<typename T::value_type>(
                        std::declval<melon::Dynamic>()))>(move(val));
            }

            // deserialization for standard containers.

            template<typename T>
            using deserializeType =
            decltype(ISerializable::deserialize<T>(std::declval<melon::Dynamic>()));

            template<typename T, std::enable_if_t<is_vector_type<T>::value>* = nullptr>
            static auto deserialize(
                const melon::Dynamic &array,
                void *context = nullptr) {
                using deserializeValType =
                        decltype(ISerializable::deserialize<typename T::value_type>(
                            std::declval<melon::Dynamic>()));

                POLLUX_USER_CHECK(array.isArray());
                std::vector<deserializeValType> exprs;
                for (auto &obj: array) {
                    exprs.push_back(
                        ISerializable::deserialize<typename T::value_type>(obj, context));
                }
                return exprs;
            }

            template<
                typename T,
                std::enable_if_t<std::is_same_v<
                    T,
                    std::map<typename T::key_type, typename T::mapped_type> > >* = nullptr>
            static auto deserialize(const melon::Dynamic &obj, void *context = nullptr) {
                return deserialize<std::map, typename T::key_type, typename T::mapped_type>(
                    obj, context);
            }

            template<
                template <typename, typename, typename...>
                typename TMap,
                typename TKey,
                typename TMapped,
                typename... TArgs,
                typename = std::enable_if_t<
                    util::is_mappish<TMap<TKey, TMapped, TArgs...> >::value &&
                    std::is_same_v<
                        typename TMap<TKey, TMapped, TArgs...>::key_type,
                        TKey> &&
                    std::is_same_v<
                        typename TMap<TKey, TMapped, TArgs...>::mapped_type,
                        TMapped>> >
            static auto deserialize(const melon::Dynamic &obj, void *context = nullptr) {
                using deserializeKeyType = decltype(ISerializable::deserialize<TKey>(
                    std::declval<melon::Dynamic>()));

                using deserializeMappedType = decltype(ISerializable::deserialize<TMapped>(
                    std::declval<melon::Dynamic>()));

                TMap<deserializeKeyType, deserializeMappedType, TArgs...> map;
                const melon::Dynamic &keys = obj["keys"];
                const melon::Dynamic &values = obj["values"];
                POLLUX_USER_CHECK(keys.isArray() && values.isArray());
                POLLUX_USER_CHECK_EQ(keys.size(), values.size());
                for (size_t idx = 0; idx < keys.size(); ++idx) {
                    auto first = ISerializable::deserialize<TKey>(keys[idx], context);
                    auto second = ISerializable::deserialize<TMapped>(values[idx], context);
                    map.insert({first, second});
                }
                return map;
            }

            virtual ~ISerializable() = default;

        private:
            template<
                typename T,
                typename = std::enable_if_t<std::is_base_of_v<ISerializable, T> > >
            static auto deserializeAsUniquePtr(const melon::Dynamic &obj) {
                auto name = obj["name"].asString();
                const auto &registry = pollux::deserializationRegistryForUniquePtr();
                using deserializeType =
                        decltype(DeserializationRegistryUniquePtrType::Create(
                            std::declval<std::string>(), std::declval<melon::Dynamic>()));
                if (registry.Has(name)) {
                    return registry.Create(name, obj);
                } else {
                    return static_cast<deserializeType>(nullptr);
                }
            }
        };
    } // namespace pollux
} // namespace kumo
